import tkinter as tk 
from tkinter import messagebox, simpledialog, filedialog
from datetime import datetime, timedelta
from lunardate import LunarDate
from lunarcalendar import Converter
import json
import os
import threading
import pystray
from PIL import Image, ImageDraw
import pandas as pd

class TodoApp:
    def __init__(self):
        self.tasks = []
        self.special_days = []
        self.base_path = os.path.dirname(__file__)
        self.tasks_file = os.path.join(self.base_path, "tasks.json")
        self.special_days_file = os.path.join(self.base_path, "special_days.json")

        self.theme_color = "#DDEEFF"
        self.mini_window = None
        self.mini_label = None
        self.tray_icon = None
        self.task_listbox = None
        self.special_days_listbox = None
        self.special_days_visible = False
        self._drag_x = 200
        self._drag_y = 0

        self.root = tk.Tk()
        self.root.withdraw()

        self.load_tasks()
        self.load_special_days()
        self.create_mini_window()
        self.create_tray_icon()
        self.check_anniversary_reminders()

        self.root.protocol("WM_DELETE_WINDOW", self.on_close)
        self.root.mainloop()

    def load_special_days(self):
        if os.path.exists(self.special_days_file):
            with open(self.special_days_file, "r", encoding="utf-8") as f:
                data = json.load(f)
                updated_data = []
                for item in data:
                    if isinstance(item, dict):
                        name = item.get("name")
                        date_str = item.get("date")
                        calendar = item.get("type", "solar")
                    elif isinstance(item, (list, tuple)):
                        if len(item) >= 3:
                            name, date_str, calendar = item[:3]
                        else:
                            continue
                    else:
                        continue

                    try:
                        date = datetime.strptime(date_str, "%m-%d-%Y")
                        anniversary = datetime.now().year - date.year
                        lunar_date_str = ""
                        if calendar == "lunar":
                            lunar_date = Converter().solar_to_lunar(date.year, date.month, date.day)
                            lunar_date_str = lunar_date.to_string()

                        updated_data.append({
                            "name": name,
                            "date": date.strftime("%m-%d-%Y"),
                            "type": calendar,
                            "anniversary": anniversary,
                            "lunar_date": lunar_date_str
                        })
                    except ValueError:
                        continue
                self.special_days = updated_data

    def show_main_window(self):
        # Check if main window is already created and visible
        if hasattr(self, 'main_window_created') and self.main_window_created:
            # Just show the existing window
            if self.root.state() == "withdrawn":
                self.root.deiconify()
            else:
                self.root.lift()
            return
        
        # Create the main window only once
        self.root.deiconify()
        self.root.title("To-Do List")
        self.root.configure(bg=self.theme_color)
        self.root.resizable(False, False)

        tk.Label(self.root, text="任务列表", bg=self.theme_color).pack()
        button_frame = tk.Frame(self.root)
        button_frame.pack(pady=5)

        tk.Button(button_frame, text="添加任务", command=self.add_task).grid(row=0, column=0, padx=5, pady=5)
        tk.Button(button_frame, text="删除任务", command=self.delete_task).grid(row=0, column=1, padx=5, pady=5)
        tk.Button(button_frame, text="完成任务", command=self.complete_task).grid(row=0, column=2, padx=5, pady=5)
        tk.Button(button_frame, text="取消完成", command=self.uncomplete_task).grid(row=0, column=3, padx=5, pady=5)
        tk.Button(button_frame, text="保存任务", command=self.save_tasks).grid(row=0, column=4, padx=5, pady=5)

        tk.Button(button_frame, text="更换主题颜色", command=self.change_theme_color).grid(row=1, column=0, padx=5, pady=5)
        tk.Button(button_frame, text="导出为文本", command=self.export_to_txt).grid(row=1, column=1, padx=5, pady=5)
        tk.Button(button_frame, text="导出为Excel", command=self.export_to_excel).grid(row=1, column=2, padx=5, pady=5)

        search_entry = tk.Entry(self.root)
        search_entry.pack(pady=5)
        search_entry.bind("<KeyRelease>", lambda e: self.update_task_listbox(search_entry.get()))

        self.task_listbox = tk.Listbox(self.root, width=50, height=10)
        self.task_listbox.pack(pady=5)
        self.task_listbox.bind("<Double-Button-1>", self.edit_selected_task)

        self.special_day_frame = tk.Frame(self.root)
        tk.Label(self.special_day_frame, text="特殊日期列表", bg=self.theme_color).pack()
        self.special_days_listbox = tk.Listbox(self.special_day_frame, width=50, height=5)
        self.special_days_listbox.pack(pady=5)
        self.update_special_days_listbox()

        special_btn_frame = tk.Frame(self.special_day_frame)
        special_btn_frame.pack()
        tk.Button(special_btn_frame, text="Add Special Day", command=self.add_special_day).pack(side=tk.LEFT, padx=5)
        tk.Button(special_btn_frame, text="Delete Selected", command=self.delete_special_day).pack(side=tk.RIGHT, padx=5)

        self.toggle_button = tk.Button(self.root, text="Show SP Days", command=self.toggle_special_days)
        self.toggle_button.pack(pady=(5, 10))

        self.update_task_listbox()
        self.update_special_days_listbox()
        
        # Mark that main window has been created
        self.main_window_created = True

    def update_task_listbox(self, keyword=""):
        if self.task_listbox:
            self.task_listbox.delete(0, tk.END)
            for i, task in enumerate(self.tasks):
                if keyword.lower() in task["title"].lower():
                    display = f"{task['title']} (截止: {task['due']})"
                    if task.get("completed", False):
                        display = "✅ " + display
                    if self.is_today(task["due"]):
                        display += " ★"

                    self.task_listbox.insert(tk.END, display)
                    if task.get("completed", False):
                        self.task_listbox.itemconfig(tk.END, {'bg': 'lightgreen'})

    def update_special_days_listbox(self):
        if self.special_days_listbox:
            self.special_days_listbox.delete(0, tk.END)
            for day in self.special_days:
                lunar_date_display = day.get('lunar_date', '无农历')
                display = f"{day['name']} - {day['date']} (农历: {lunar_date_display}) (周年: {day['anniversary']})"
                self.special_days_listbox.insert(tk.END, display)

    def add_special_day(self):
        name = simpledialog.askstring("添加特殊日期", "特殊日期名称：")
        if name:
            date = simpledialog.askstring("日期", "格式 yyyy-mm-dd：")
            if date:
                try:
                    gregorian_date = datetime.strptime(date, "%Y-%m-%d")
                    lunar_date = Converter().solar_to_lunar(gregorian_date.year, gregorian_date.month, gregorian_date.day)
                    lunar_date_str = lunar_date.to_string()

                    self.special_days.append({
                        "name": name,
                        "date": date,
                        "type": "solar",
                        "lunar_date": lunar_date_str
                    })
                    self.update_special_days_listbox()
                except ValueError:
                    messagebox.showerror("错误", "日期格式应为 yyyy-mm-dd")

    def delete_special_day(self):
        selection = self.special_days_listbox.curselection()
        if selection:
            index = selection[0]
            del self.special_days[index]
            self.update_special_days_listbox()

    def toggle_special_days(self):
        if self.special_days_visible:
            self.special_day_frame.pack_forget()
            self.toggle_button.config(text="Show SP Days")
        else:
            self.special_day_frame.pack(pady=(5, 10))
            self.toggle_button.config(text="Hide SP Days")
        self.special_days_visible = not self.special_days_visible

    def add_task(self):
        title = simpledialog.askstring("添加任务", "任务标题：")
        if title:
            due = simpledialog.askstring("任务截止日期", "格式 YYYY-MM-DD：")
            try:
                datetime.strptime(due, "%Y-%m-%d")
                self.tasks.append({"title": title, "due": due, "completed": False})
                self.update_task_listbox()
            except ValueError:
                messagebox.showerror("错误", "日期格式应为 YYYY-MM-DD")

    def edit_selected_task(self, event=None):
        selection = self.task_listbox.curselection()
        if not selection:
            return
        index = selection[0]
        task = self.tasks[index]
        new_title = simpledialog.askstring("编辑任务", "新标题：", initialvalue=task["title"])
        new_due = simpledialog.askstring("编辑截止日期", "格式 YYYY-MM-DD：", initialvalue=task["due"])
        if new_title and new_due:
            try:
                datetime.strptime(new_due, "%Y-%m-%d")
                task["title"], task["due"] = new_title, new_due
                self.update_task_listbox()
                self.shrink_mini_window()
            except ValueError:
                messagebox.showerror("错误", "日期格式应为 YYYY-MM-DD")

    def delete_task(self):
        selection = self.task_listbox.curselection()
        if selection:
            index = selection[0]
            del self.tasks[index]
            self.update_task_listbox()
            self.shrink_mini_window()

    def complete_task(self):
        selection = self.task_listbox.curselection()
        if selection:
            index = selection[0]
            if 0 <= index < len(self.tasks):
                self.tasks[index]["completed"] = True
                self.update_task_listbox()
                self.shrink_mini_window()

    def uncomplete_task(self):
        selection = self.task_listbox.curselection()
        if selection:
            index = selection[0]
            if 0 <= index < len(self.tasks):
                self.tasks[index]["completed"] = False
                self.update_task_listbox()
                self.shrink_mini_window()

    def save_tasks(self):
        with open(self.tasks_file, "w", encoding="utf-8") as f:
            json.dump(self.tasks, f, ensure_ascii=False, indent=2)
        with open(self.special_days_file, "w", encoding="utf-8") as f:
            json.dump(self.special_days, f, ensure_ascii=False, indent=2)

    def load_tasks(self):
        if os.path.exists(self.tasks_file):
            with open(self.tasks_file, "r", encoding="utf-8") as f:
                self.tasks = json.load(f)

    def change_theme_color(self):
        color = simpledialog.askstring("主题颜色", "输入颜色代码（如 #DDEEFF）：", initialvalue=self.theme_color)
        if color:
            self.theme_color = color
            self.root.configure(bg=self.theme_color)

    def on_close(self):
        if messagebox.askyesno("退出提示", "是否保存并退出？"):
            self.save_tasks()
            self.root.withdraw()  # Hide instead of destroy
        else:
            self.root.withdraw()  # Hide instead of destroy

    def reopen_main_window(self):
        # If already exists, just show it again
        if self.root.state() == "withdrawn":
            self.root.deiconify()
        else:
            self.root.lift()  # bring to front if already open      

    def export_to_txt(self):
        path = filedialog.asksaveasfilename(defaultextension=".txt")
        if path:
            with open(path, "w", encoding="utf-8") as f:
                for task in self.tasks:
                    status = "完成" if task.get("completed", False) else "未完成"
                    f.write(f"{task['title']} - {task['due']} - {status}\n")
            messagebox.showinfo("导出成功", "任务已导出为文本文件")

    def export_to_excel(self):
        path = filedialog.asksaveasfilename(defaultextension=".xlsx")
        if path:
            df = pd.DataFrame(self.tasks)
            df.to_excel(path, index=False)
            messagebox.showinfo("导出成功", "任务已导出为 Excel 文件")

    def is_today(self, date_str):
        try:
            return datetime.strptime(date_str, "%Y-%m-%d").date() == datetime.today().date()
        except ValueError:
            return False

    def check_anniversary_reminders(self):
        today = datetime.today()
        for day in self.special_days:
            try:
                name = day.get("name", "")
                date_obj = datetime.strptime(day.get("date", ""), "%m-%d-%Y")
                if (today.month == date_obj.month and today.day == date_obj.day) or \
                   (LunarDate.fromSolarDate(today.year, today.month, today.day).month == date_obj.month and
                    LunarDate.fromSolarDate(today.year, today.month, today.day).day == date_obj.day):
                    messagebox.showinfo("纪念日提醒", f"今天是 {name}")
            except ValueError:
                continue

    def is_within_due_date_range(self, due_date):
        today = datetime.now().date()
        due_date_obj = datetime.strptime(due_date, "%Y-%m-%d").date()
        return (due_date_obj - today).days <= 5 and (due_date_obj - today).days >= 0

    def create_mini_window(self):
        self.mini_window = tk.Toplevel()
        self.mini_window.title("任务提醒")
        self.mini_window.geometry("100x40")
        self.mini_window.overrideredirect(True)
        self.mini_window.wm_attributes("-topmost", True)
        has_due_or_approaching_task = any( self.is_today(task["due"]) or self.is_within_due_date_range(task["due"]) for task in self.tasks )
        if has_due_or_approaching_task:
            self.mini_window.wm_attributes("-alpha", 0.5)
            bg_color = "red" # 红色背景
        else:
            self.mini_window.wm_attributes("-alpha", 1.0)
            bg_color = "lightgray"
        screen_width = self.mini_window.winfo_screenwidth()
        screen_height = self.mini_window.winfo_screenheight()
        x_position = (screen_width // 2) - 50 # 居中
        y_position = 0
        self.mini_window.geometry(f"100x40+{x_position}+{y_position}")
        self.mini_label = tk.Label(self.mini_window, text=f"{len(self.tasks)}", bg=bg_color, font=("Arial", 10))
        self.mini_label.pack(fill="both", expand=True)

        self.shrink_mini_window()

        self.mini_window.bind("<Enter>", self.expand_mini_window)
        self.mini_window.bind("<Leave>", self.shrink_mini_window)
        self.mini_window.bind("<ButtonPress-1>", self.start_drag)
        self.mini_window.bind("<B1-Motion>", self.do_drag)

    def expand_mini_window(self, event=None):
        self.mini_window.geometry("")
        for widget in self.mini_window.winfo_children():
            widget.destroy()

        tip_label = tk.Label(self.mini_window, text="💡 右键点击任务可打开主菜单", fg="gray", font=("Arial", 10, "italic"))
        tip_label.pack(fill="x", pady=(5, 10))

        for task in self.tasks:
            if task.get("completed", False):
                continue  # 已完成任务不显示在mini window
            title = task["title"]
            due_date = task["due"]
            text = f"{title} (截止: {due_date})"
            bg_color = "red" if self.is_today(due_date) or self.is_within_due_date_range(due_date) else "white"
            label = tk.Label(self.mini_window, text=text, bg=bg_color, anchor="w", font=("Arial", 15))
            label.pack(fill="x")
        
        self.mini_window.bind("<Button-3>", lambda event: self.show_main_window())

    def shrink_mini_window(self, event=None):
        self.mini_window.geometry("100x40")
        for widget in self.mini_window.winfo_children():
            widget.destroy()

        has_due_or_approaching_task = any(
            (not task.get("completed", False)) and (self.is_today(task["due"]) or self.is_within_due_date_range(task["due"]))
            for task in self.tasks
        )
        if has_due_or_approaching_task:
            self.mini_window.wm_attributes("-alpha", 0.5)
            bg_color = "red"
        else:
            self.mini_window.wm_attributes("-alpha", 1.0)
            bg_color = "lightgray"

        active_tasks = [t for t in self.tasks if not t.get("completed", False)]
        self.mini_label = tk.Label(self.mini_window, text=f"{len(active_tasks)}", bg=bg_color, font=("Arial", 10))
        self.mini_label.pack(fill="both", expand=True)

    def create_tray_icon(self):
        image = Image.new("RGB", (64, 64), color="gray")
        draw = ImageDraw.Draw(image)
        draw.rectangle([16, 16, 48, 48], fill="Red")
        self.tray_icon = pystray.Icon("todo", image, "待办事项", menu=pystray.Menu(
            pystray.MenuItem("显示主界面", lambda: self.show_main_window()),
            pystray.MenuItem("退出", lambda: self.exit_app())
        ))
        threading.Thread(target=self.tray_icon.run, daemon=True).start()

    def exit_app(self):
        if self.tray_icon:
            self.tray_icon.stop()
        self.save_tasks()
        self.root.quit()

    def start_drag(self, event):
        self._drag_x = event.x
        self._drag_y = event.y

    def do_drag(self, event):
        x = self.mini_window.winfo_x() + event.x - self._drag_x
        y = self.mini_window.winfo_y() + event.y - self._drag_y
        self.mini_window.geometry(f"+{x}+{y}")

if __name__ == "__main__":
    app = TodoApp()
